/**
 * 
 */
package me.twoday.library.mine.common.base;

import java.io.InputStream;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Stack;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import me.twoday.library.mine.common.LoggerUtil;
import me.twoday.library.mine.common.SharedUtil;
import me.twoday.library.mine.common.image.ImageFileCache;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

/**
 * 어플리케이션의 기본이 되는 글로벌 객체
 * 
 * @author 박우람
 * @since 2010.02.13
 */
public class BaseApplication extends Application
{
	private static final String TAG = "BaseApplication";
	private final LoggerUtil mLog;
	
	private static BaseApplication mInstance;
//  옵션 관련 메뉴를 사용하지 않으므로 주석처리 해놓습니다.
//	private BaseOptionMenu mOptionMenu;
	private Bundle mGlobalPersistent;
	private Hashtable<String, String> mConfigPersistent;
	private Stack<Activity> mActivityStack;
	private Class<?> mErrorActivity;
	private Class<?> mPushActivity;
	private boolean mIsFirstActivity;
	
	/**
	 * 프로그램 전역에서 접근할 수 있는 어플리케이션 객체를 정의한다.
	 */
	@SuppressWarnings("static-access")
	public BaseApplication()
	{
		// 어디서든지 접근할 수 있는 전역 객체 선언
		this.mInstance = this;
		this.mGlobalPersistent = new Bundle();
		this.mConfigPersistent = new Hashtable<String, String>();
		this.mActivityStack = new Stack<Activity>();
		this.mIsFirstActivity = true;
		this.mLog = LoggerUtil.getInstance(TAG);
		
		// 어플리케이션의 사용자 설정을 읽어옵니다.
		initConfigPersistent();
		
		// 옵션메뉴를 설정합니다. (현재 사용하지 않으므로 주석처리 합니다.)
		//initOptionMenu();
		
		// 에러 엑티비티를 설정합니다.
		initErrorActivity();
		
		// 푸시 수신시에 AlertDialog창으로 띄우기 위한 엑티비티를 설정합니다.
		initPushActivity();
		
		// 오래된 이미지 캐시파일을 삭제합니다.
		ImageFileCache.cleanCaches();
	}

	/**
	 * 새로운 엑티비티 생성시에 엑티비티 히스토리를 저장합니다.
	 * BaseActivity에 구현이 되어있으며 사용하지 않도록 합니다. 
	 * 
	 * @param activity 추가할 엑티비티
	 */
	public static void pushActivityStack(Activity activity)
	{
		if(mInstance.mIsFirstActivity == true)
		{
			Intent intent = activity.getIntent();
			restoreBackupedState(intent.getBundleExtra("_APPLICATION_BACKUP_BUNDLE"));
			mInstance.mIsFirstActivity = false;
			mInstance.mLog.d("어플리케이션 데이터를 복원하였습니다");
		}
		
		mInstance.mActivityStack.push(activity);
		mInstance.mLog.d("엑티비티가 생성되었습니다 (" + mInstance.mActivityStack.size() + ")");
	}
	
	/**
	 * 엑티비티 삭제시 엑티비티 히스토리로부터 삭제합니다.
	 * BaseActivity에 구현이 되어있으며 사용하지 않도록 합니다.
	 * 
	 * @param activity 삭제할 엑티비티
	 */
	public static void popActivityStack(Activity activity)
	{
		if(!mInstance.mActivityStack.isEmpty())
		{
			mInstance.mActivityStack.remove(activity);
			mInstance.mLog.d("엑티비티가 삭제되었습니다 (" + mInstance.mActivityStack.size() + ")");
		}
		
		if(mInstance.mActivityStack.size() == 0)
		{
			// 어플리케이션을 완벽하게 종료하기 위해 호출, 하지만 명시적으로 shutdown()을 호출하는것을 권장
			// 아래의 주석을 해제할 경우 엑티비티간 전환중에 순간적으로 스택이 0이 될 경우 앱이 종료될 수 있음
			//forceShutdown();
		}
	}
	
	/**
	 * 어플리케이션을 완벽하게 메모리에서 소거하며 종료합니다.
	 */
	public static void forceShutdown()
	{
		try
		{
			if(mInstance.mActivityStack != null) mInstance.mActivityStack.clear();
			if(mInstance.mConfigPersistent != null) mInstance.mConfigPersistent.clear();
//			옵션을 사용하지 않으므로 수석처리 
//			mInstance.mOptionMenu = null;
			mInstance.mGlobalPersistent = null;
			mInstance.mConfigPersistent = null;
			mInstance.mActivityStack = null;
			mInstance.mErrorActivity = null;
			mInstance.mPushActivity = null;
			mInstance.mIsFirstActivity = false;
			mInstance = null;
		}
		catch(Exception e) {}
		android.os.Process.killProcess(android.os.Process.myPid());
	}
	
	/**
	 * 엑티비티 스택중 최상위 엑티비티를 반환합니다.
	 * 
	 * @return 최상위 엑티비티
	 */
	public static Activity getTopActivity()
	{
		if(mInstance.mActivityStack.isEmpty())
		{
			return null;
		}
		else
		{
			return mInstance.mActivityStack.peek();
		}
	}
	
	/**
	 * 테스크상에 존재하는 모든 엑티비티를 삭제합니다.
	 * ActivityUtil의 setRemoveTask()메서드를 사용하여 간접호출 할수 있으며 사용하지 않도록 합니다.
	 */
	public static void resetActivityStack()
	{
		Activity activity;
		while(!mInstance.mActivityStack.isEmpty())
		{
			activity = mInstance.mActivityStack.pop();
			activity.finish();
		}
	}
	
	/**
	 * 엑티비티 스택을 반환합니다.
	 * 
	 * @return 엑티비티 스택
	 */
	public static Stack<Activity> getActivityStack()
	{
		return mInstance.mActivityStack;
	}
	
	/**
	 * 어플리케이션 컨텍스트를 반환한다.
	 * 이 컨텍스트를 사용할때는 화면에 영향을 주는 용도로 사용하면 메모리릭을 발생할 수 있으니 주의하자.
	 * 
	 * @return Context 객체
	 */
	public static Context getContext()
	{
		return mInstance.getApplicationContext();
	}
	
	/**
	 * 세션키값을 가져옵니다.
	 * 
	 * @return 세션키
	 */
	public static String getSessionKey()
	{
		return SharedUtil.getInstance().getSharedPreference("_APPLICATION_SESSION_KEY");
	}
	
	/**
	 * 세션키값을 설정합니다.
	 * 
	 * @param sessionKeys 세션키
	 */
	public static void setSessionKey(String sessionKeys)
	{
		String oldSessionKey = SharedUtil.getInstance().getSharedPreference("_APPLICATION_SESSION_KEY");
		if(oldSessionKey == null)
		{
			mInstance.mLog.d("새로운 사용자 세션이 정의되었습니다: " + sessionKeys);
			SharedUtil.getInstance().putSharedPreference("_APPLICATION_SESSION_KEY", sessionKeys);
		}
	}
	
	/**
	 * 세션키값을 삭제합니다. 로그인등의 작업으로 세션키 값을 바꿔야 할때 삭제하도록 합니다.
	 */
	public static void clearSessionKey()
	{
		SharedUtil.getInstance().putSharedPreference("_APPLICATION_SESSION_KEY", null);
	}
	
	/**
	 * 현재 어플리케이션 상태를 백업합니다.
	 * 
	 * @return 어플리케이션의 상태값
	 */
	public static Bundle backupCurrentState()
	{
		Bundle backup = new Bundle();
		backup.putBundle("_APPLICATION_BACKUP_GLOBAL_PERSISTENT", mInstance.mGlobalPersistent);
		return backup;
	}
	
	/**
	 * 현재 어플리케이션 상태를 복원합니다.
	 * 
	 * @param backup 이전 어플리케이션 백업 번들
	 */
	public static void restoreBackupedState(Bundle backup)
	{
		if(backup != null)
		{
			mInstance.mGlobalPersistent = backup.getBundle("_APPLICATION_BACKUP_GLOBAL_PERSISTENT");
		}
	}
	
	/**
	 * 임의로 사용할 전역 변수의 값을 가져옵니다. 가급적 필수가 아닌이상 사용하지 않도록 합니다.
	 * 
	 * @param key 변수 키
	 * @return 변수 값
	 */
	public static Object getGlobalPersistent(String key)
	{
		return mInstance.mGlobalPersistent.getSerializable(key);
	}
	
	/**
	 * 임의로 사용할 전역 변수의 값을 설정합니다. 가급적 필수가 아닌이상 사용하지 않도록 합니다.
	 * 
	 * @param key 변수 키
	 * @param value 변수 값
	 */
	public static void setGlobalPersistent(String key, Object value)
	{
		mInstance.mGlobalPersistent.putSerializable(key, (Serializable) value);
	}
	
	/**
	 * 설정값을 가져옵니다.
	 * 
	 * @param key 설정키
	 */
	public static String getConfigPersistent(String key)
	{
		return mInstance.mConfigPersistent.get(key);
	}
	
	/**
	 * Gateway를 변경합니다.
	 * 
	 * @param gateway
	 * @return
	 */
	public static String setGateway(String gateway)
	{
		return mInstance.mConfigPersistent.put("gateway", gateway);
	}
	
	/**
	 * Gateway를 변경합니다.
	 * 
	 * @param key
	 * @param gateway
	 * @return
	 */
	public static String setGateway(String key, String gateway)
	{
		return mInstance.mConfigPersistent.put(key, gateway);
	}
	
	/**
	 * 어플리케이션의 기본 설정값을 읽어서 전역 변수로 지정합니다.
	 * 이후에 getConfigPersistent 메서드를 이용하여 값을 꺼내쓸 수 있습니다.
	 */
	private void initConfigPersistent()
	{
		try
		{
			// XML로 된 설정 파일을 읽습니다.
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			InputStream stream = getClass().getResourceAsStream("/config.xml");
			Document document = builder.parse(stream);
			
			// 태그명이 config인 엘리먼트 노드들을 읽어옵니다.
			Element config = document.getDocumentElement();
			NodeList configList = config.getElementsByTagName("config");
			readNodeList(configList);
			
			// 게이트웨이 설정을 읽어옵니다. 첫번째 설정이 기본 설정입니다.
			NodeList gatewayList = config.getElementsByTagName("gateway");
			
			if(gatewayList.getLength() > 0)
			{
				Node node = gatewayList.item(0);
				NodeList hostList = node.getChildNodes();
				readNodeList(hostList);
			}
			else
			{
				Log.e(TAG, "통신을 할 게이트웨이 호스트 설정을 찾지 못하였습니다");
			}
		}
		catch(Exception e)
		{
			Log.e(TAG, "설정파일을 읽어오는데에 실패하였습니다. config.xml이 존재하는지 확인하십시오");
		}
	}
	
	/**
	 * config.xml로 부터 읽어들인 설정을 순환하며 읽은 후에 전역변수로 저장합니다.
	 * 
	 * @param nodeList 노드 리스트
	 */
	private void readNodeList(NodeList nodeList)
	{
		// 노드를 순환하며 값을 읽어옵니다.
		for(int i = 0 ; i < nodeList.getLength() ; i++)
		{
			Node node = nodeList.item(i);
			
			// 일반적인 엘리먼트 노드가 아닐 경우 처리를 하지 않는다.
			if(node.getNodeType() != Node.ELEMENT_NODE) continue;
			
			NamedNodeMap attrs = node.getAttributes();
			
			String key = null;
			String value = null;
			
			for(int j = 0 ; j <attrs.getLength() ; j++)
			{
				Node attr = attrs.item(j);
				
				if(attr.getNodeName().equals("key"))
				{
					key = attr.getNodeValue();
				}
				else if(attr.getNodeName().equals("value"))
				{
					value = attr.getNodeValue();
				}
			}
			
			// 올바른 설정값이 존재할경우 값을 저장합니다.
			if(key != null && value != null)
			{
				mConfigPersistent.put(key, value);
				//Log.d(TAG, "새로운 설정을 등록하였습니다: " + key + " = " + value);
			}
			else
			{
				Log.e(TAG, "새로운 설정을 읽어오는것을 실패하였습니다: " + key + " = " + value);
			}
		}
	}
	
	/**
	 * 옵션메뉴를 설정합니다.
	 */
//	@SuppressWarnings("rawtypes")
//	private void initOptionMenu()
//	{
//		String optionMenuClass = mConfigPersistent.get("optionClass");
//		try
//		{
//			Class cls = Class.forName(optionMenuClass);
//			this.mOptionMenu = (BaseOptionMenu) cls.newInstance();
//			
//			mInstance.mLog.d("옵션메뉴를 동적으로 로드하였습니다");
//		}
//		catch(Exception e)
//		{
//			mInstance.mLog.e("옵션메뉴를 동적으로 로드하는데에 실패하였습니다. optionClass 설정을 확인하십시오");
//		}
//	}
	
	/**
	 * 옵션메뉴 구현부를 반환합니다.
	 * 
	 * @return 옵션메뉴 구현 객체
	 */
//	public static BaseOptionMenu getOptionMenu()
//	{
//		return mInstance.mOptionMenu;
//	}
	
	/**
	 * 에러 발생시 보여줄 엑티비티를 설정합니다.
	 */
	private void initErrorActivity()
	{
		String errorActivityClass = mConfigPersistent.get("errorClass");
		try
		{
			this.mErrorActivity = Class.forName(errorActivityClass);
			
			Log.d(TAG, "에러엑티비티 클래스를 동적으로 로드하였습니다");
		}
		catch(Exception e)
		{
			Log.e(TAG, "에러엑티비티를 동적으로 로드하는데에 실패하였습니다. errorClass 설정을 확인하십시오");
		}
	}
	
	/**
	 * 푸시 메시지가 도착시 보여줄 엑티비티를 설정합니다.
	 */
	private void initPushActivity()
	{
		String pushActivityClass = mConfigPersistent.get("pushClass");
		try
		{
			this.mPushActivity = Class.forName(pushActivityClass);
			
			mInstance.mLog.d("푸시 수신용 엑티비티 클래스를 동적으로 로드하였습니다");
		}
		catch(Exception e)
		{
			mInstance.mLog.e("푸시엑티비티를 동적으로 로드하는데에 실패하였습니다. pushClass 설정을 확인하십시오");
		}
	}
	
	/**
	 * 에러로 사용할 엑티비티를 설정합니다.
	 * 
	 * @param activityClass 에러 엑티비티 클래스
	 */
	public static void setErrorActivity(Class<?> activityClass)
	{
		mInstance.mErrorActivity = activityClass;
	}
	
	/**
	 * 에러 엑티비티 클래스
	 * 
	 * @return 엑티비티 클래스
	 */
	public static Class<?> getErrorActivity()
	{
		return mInstance.mErrorActivity;
	}
	
	/**
	 * 푸시 엑티비티 클래스
	 * 
	 * @return 엑티비티 클래스
	 */
	public static Class<?> getPushActivity()
	{
		return mInstance.mPushActivity;
	}
}
